<!DOCTYPE html>
<html>
  <head>
  <meta http-equiv="content-type" content="text/html; charset=utf-8">
  <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport">
  <meta name="description" content="tong.li&#39;s blog">
  <meta name="keyword" content="彤哥哥博客，95后技术爱好者,现就职于同程旅行/同程艺龙上海分公司，专注于互联网技术分享的平台。">
  
    <link rel="shortcut icon" href="/css/images/icon.png">
  
  <title>
    
      全方位玩转Redis | 彤哥哥的博客
    
  </title>
  <link href="https://cdn.staticfile.org/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
  <link href="https://cdn.staticfile.org/nprogress/0.2.0/nprogress.min.css" rel="stylesheet">
  <link href="https://cdn.staticfile.org/highlight.js/9.12.0/styles/tomorrow-night.min.css" rel="stylesheet">
  
<link rel="stylesheet" href="/css/style.css">

  
  <script src="https://cdn.staticfile.org/jquery/3.2.1/jquery.min.js"></script>
  <script src="https://cdn.staticfile.org/geopattern/1.2.3/js/geopattern.min.js"></script>
  <script src="https://cdn.staticfile.org/nprogress/0.2.0/nprogress.min.js"></script>
  
    
<script src="/js/qrious.js"></script>

  
  
  
  
    <!-- MathJax support START -->
    <script type="text/x-mathjax-config">
      MathJax.Hub.Config({
        tex2jax: {
          inlineMath: [ ['$','$'], ["\\(","\\)"]  ],
          processEscapes: true,
          skipTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code']
        }
      });
    </script>

    <script type="text/x-mathjax-config">
      MathJax.Hub.Queue(function() {
        var all = MathJax.Hub.getAllJax(), i;
        for (i=0; i < all.length; i += 1) {
          all[i].SourceElement().parentNode.className += ' has-jax';
        }
      });
    </script>
    <script type="text/javascript" src="https://cdn.staticfile.org/mathjax/2.7.5/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
    <!-- MathJax support END -->
  


  
  
    
<script src="/js/local-search.js"></script>


<meta name="generator" content="Hexo 5.4.2"></head>
<div class="wechat-share">
  <img src="/css/images/logo.png" />
</div>
  <body>
    <header class="header fixed-header">
  <div class="header-container">
    <a class="home-link" href="/">
      <div class="logo"></div>
      <span>彤哥哥的博客</span>
    </a>
    <ul class="right-list">
      
        <li class="list-item">
          
            <a href="/" class="item-link">主页</a>
          
        </li>
      
        <li class="list-item">
          
            <a href="/series/" class="item-link">分类</a>
          
        </li>
      
        <li class="list-item">
          
            <a href="/tags/" class="item-link">标签</a>
          
        </li>
      
        <li class="list-item">
          
            <a href="/archives/" class="item-link">归档</a>
          
        </li>
      
        <li class="list-item">
          
            <a href="/project/" class="item-link">项目</a>
          
        </li>
      
        <li class="list-item">
          
            <a href="/about/" class="item-link">关于</a>
          
        </li>
      
      
        <li class="menu-item menu-item-search right-list">
    <a role="button" class="popup-trigger">
        <i class="fa fa-search fa-fw"></i>
    </a>
</li>
      
    </ul>
    <div class="menu">
      <span class="icon-bar"></span>
      <span class="icon-bar"></span>
      <span class="icon-bar"></span>
    </div>
    <div class="menu-mask">
      <ul class="menu-list">
        
          <li class="menu-item">
            
              <a href="/" class="menu-link">主页</a>
            
          </li>
        
          <li class="menu-item">
            
              <a href="/series/" class="menu-link">分类</a>
            
          </li>
        
          <li class="menu-item">
            
              <a href="/tags/" class="menu-link">标签</a>
            
          </li>
        
          <li class="menu-item">
            
              <a href="/archives/" class="menu-link">归档</a>
            
          </li>
        
          <li class="menu-item">
            
              <a href="/project/" class="menu-link">项目</a>
            
          </li>
        
          <li class="menu-item">
            
              <a href="/about/" class="menu-link">关于</a>
            
          </li>
        
      </ul>
    </div>
    
      <div class="search-pop-overlay">
    <div class="popup search-popup">
        <div class="search-header">
            <span class="search-icon">
                <i class="fa fa-search"></i>
            </span>
            <div class="search-input-container">
                <input autocomplete="off" autocapitalize="off"
                    placeholder="Please enter your keyword(s) to search." spellcheck="false"
                    type="search" class="search-input">
            </div>
            <span class="popup-btn-close">
                <i class="fa fa-times-circle"></i>
            </span>
        </div>
        <div id="search-result">
            <div id="no-result">
                <i class="fa fa-spinner fa-pulse fa-5x fa-fw"></i>
            </div>
        </div>
    </div>
</div>
    
  </div>
</header>

    <div id="article-banner">
  <h2>全方位玩转Redis</h2>
  <p class="post-date">2023-01-07</p>
  <div class="arrow-down">
    <a href="javascript:;"></a>
  </div>
</div>
<main class="app-body flex-box">
  <!-- Article START -->
  <article class="post-article">
    <section class="markdown-content"><h1 id="常用类型"><a href="#常用类型" class="headerlink" title="常用类型"></a>常用类型</h1><h2 id="String"><a href="#String" class="headerlink" title="String"></a>String</h2><p>字符串，基本的数据结构，不多说什么。</p>
<p>这里着重看一下<a target="_blank" rel="noopener" href="https://redis.io/commands/setnx"><code>SETNX</code></a>和<a target="_blank" rel="noopener" href="https://redis.io/commands/setex"><code>SETEX</code></a>命令。</p>
<h3 id="基本操作"><a href="#基本操作" class="headerlink" title="基本操作"></a>基本操作</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">127.0.0.1:6379&gt; <span class="built_in">help</span>   <span class="comment"># 帮助命令</span></span><br><span class="line">redis-cli 3.0.504</span><br><span class="line">Type: <span class="string">&quot;help @&lt;group&gt;&quot;</span> to get a list of commands <span class="keyword">in</span> &lt;group&gt;</span><br><span class="line">      <span class="string">&quot;help &lt;command&gt;&quot;</span> <span class="keyword">for</span> <span class="built_in">help</span> on &lt;<span class="built_in">command</span>&gt;</span><br><span class="line">      <span class="string">&quot;help &lt;tab&gt;&quot;</span> to get a list of possible <span class="built_in">help</span> topics</span><br><span class="line">      <span class="string">&quot;quit&quot;</span> to <span class="built_in">exit</span></span><br><span class="line">127.0.0.1:6379&gt; <span class="built_in">help</span> <span class="built_in">set</span>   <span class="comment"># 查看set命令使用文档</span></span><br><span class="line"></span><br><span class="line">  SET key value [EX seconds] [PX milliseconds] [NX|XX]</span><br><span class="line">  summary: Set the string value of a key</span><br><span class="line">  since: 1.0.0</span><br><span class="line">  group: string</span><br></pre></td></tr></table></figure>

<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line">127.0.0.1:6379&gt; <span class="built_in">set</span> name litong   <span class="comment"># 存取字符串值</span></span><br><span class="line">OK </span><br><span class="line">127.0.0.1:6379&gt; get name    <span class="comment"># 获取字符串值</span></span><br><span class="line"><span class="string">&quot;litong&quot;</span></span><br><span class="line">127.0.0.1:6379&gt; ttl name  <span class="comment"># 当前key的有效期，两个特例：-2表示Key不存在或者已过期，-1表示永久有效</span></span><br><span class="line">(<span class="built_in">integer</span>) -1</span><br><span class="line">127.0.0.1:6379&gt; <span class="built_in">set</span> age 28 ex 100  <span class="comment"># 存取字符串值并设置有效期，该命令具备原子性。</span></span><br><span class="line">OK</span><br><span class="line">127.0.0.1:6379&gt; ttl age   </span><br><span class="line">(<span class="built_in">integer</span>) 97   </span><br><span class="line">127.0.0.1:6379&gt; expire name 100  <span class="comment"># 当然也可以单独设置有效期，不过不具备原子特性</span></span><br><span class="line">(<span class="built_in">integer</span>) 1</span><br><span class="line">127.0.0.1:6379&gt; mset name litong age 28  <span class="comment"># 设置多个字符串值</span></span><br><span class="line">OK</span><br><span class="line">127.0.0.1:6379&gt; mget name age  <span class="comment"># 获取多个值</span></span><br><span class="line">1) <span class="string">&quot;litong&quot;</span></span><br><span class="line">2) <span class="string">&quot;28&quot;</span></span><br><span class="line">127.0.0.1:6379&gt; incr age  <span class="comment"># 原子性自增</span></span><br><span class="line">(<span class="built_in">integer</span>) 29</span><br><span class="line">127.0.0.1:6379&gt; incrby age 10 <span class="comment"># 按步长进行原子性自增</span></span><br><span class="line">(<span class="built_in">integer</span>) 38</span><br><span class="line">127.0.0.1:6379&gt; incrby age -10  <span class="comment"># 按步长进行原子性自减</span></span><br><span class="line">(<span class="built_in">integer</span>) 18</span><br><span class="line">127.0.0.1:6379&gt; decr age  <span class="comment"># 原子性自减</span></span><br><span class="line">(<span class="built_in">integer</span>) 27</span><br><span class="line">127.0.0.1:6379&gt; decrby age 10  <span class="comment"># 按步长进行原子性自减</span></span><br><span class="line">(<span class="built_in">integer</span>) 17</span><br><span class="line">127.0.0.1:6379&gt; exists name  <span class="comment"># 判断是否存在name的key</span></span><br><span class="line">(<span class="built_in">integer</span>) 1</span><br><span class="line">127.0.0.1:6379&gt; exists sxdf  <span class="comment"># 判断是否存在sxdf的key</span></span><br><span class="line">(<span class="built_in">integer</span>) 0</span><br><span class="line">127.0.0.1:6379&gt; setnx age 18 <span class="comment"># 仅当键不存在时才存储字符串值。该命令具备原子性，用于分布式实现锁</span></span><br><span class="line">(<span class="built_in">integer</span>) 0</span><br><span class="line">127.0.0.1:6379&gt; get age  <span class="comment"># 上述命令返回0表示set失败，那么获取还是原值</span></span><br><span class="line"><span class="string">&quot;17&quot;</span></span><br><span class="line">127.0.0.1:6379&gt; get xx  <span class="comment"># 这个key不存在</span></span><br><span class="line">(nil)</span><br><span class="line">127.0.0.1:6379&gt; setnx xx 18  <span class="comment"># setnx设置成功了</span></span><br><span class="line">(<span class="built_in">integer</span>) 1</span><br><span class="line">127.0.0.1:6379&gt; get xx  <span class="comment"># set成功的值</span></span><br><span class="line"><span class="string">&quot;18&quot;</span></span><br><span class="line">127.0.0.1:6379&gt; getset age 28  <span class="comment"># 设置新值，返回旧值，注意这个命令从6.2.0版本开始弃用，替换为setwith</span></span><br><span class="line"><span class="string">&quot;18&quot;</span></span><br><span class="line">127.0.0.1:6379&gt; get age</span><br><span class="line"><span class="string">&quot;28&quot;</span></span><br></pre></td></tr></table></figure>

<h3 id="扩展说明"><a href="#扩展说明" class="headerlink" title="扩展说明"></a>扩展说明</h3><ol>
<li><p>默认情况下，单个 Redis 字符串最大为 512 MB。Redis是C语言写的，数据类型长度字段len用32位的int表示，2^32最大就是512M字节。</p>
</li>
<li><p>大多数字符串操作的时间复杂度为O(1)，有些不常用的命令时间复杂度可能会达到O(n)，如：<a target="_blank" rel="noopener" href="https://redis.io/commands/substr"><code>SUBSTR</code></a>、 <a target="_blank" rel="noopener" href="https://redis.io/commands/getrange"><code>GETRANGE</code></a>、<a target="_blank" rel="noopener" href="https://redis.io/commands/setrange"><code>SETRANGE</code></a>，<a target="_blank" rel="noopener" href="https://redis.io/commands/lcs/"><code>LCS</code></a>命令时间复杂度也能达到O(n * m)，详细请看命令指南。</p>
</li>
<li><p>自增、自减操作仅限于64 位有符号整数。</p>
</li>
<li><p>应用场景：计数器、JSON字符串信息、标志位信息、字符串按位运算</p>
</li>
<li><p>相比Java的String，但Redis的String可以动态扩容</p>
</li>
</ol>
<h2 id="Hash"><a href="#Hash" class="headerlink" title="Hash"></a>Hash</h2><p>  哈希Hash结构日常使用率也比较高，一般用于存放结构化的数据。主要是描述Key、Filed以及Field Value三者之间的关系，可以描述业务代码中对象与字段的关系。</p>
<h3 id="基本操作-1"><a href="#基本操作-1" class="headerlink" title="基本操作"></a>基本操作</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br></pre></td><td class="code"><pre><span class="line">127.0.0.1:6379&gt; hset user:01 <span class="built_in">id</span> 1  <span class="comment"># 设置Key为user:01中字段为id的值</span></span><br><span class="line">(<span class="built_in">integer</span>) 1</span><br><span class="line">127.0.0.1:6379&gt; hget user:01 <span class="built_in">id</span>  <span class="comment"># 获取Key为user:01中字段为id的值</span></span><br><span class="line"><span class="string">&quot;1&quot;</span></span><br><span class="line">127.0.0.1:6379&gt; hmset user:01 name litong age 28 <span class="comment"># 设置user:01多个字段及字段值</span></span><br><span class="line">OK</span><br><span class="line">127.0.0.1:6379&gt; hmget user:01 <span class="built_in">id</span> name age <span class="comment"># 获取Key为user:01中多个字段的值</span></span><br><span class="line">1) <span class="string">&quot;1&quot;</span></span><br><span class="line">2) <span class="string">&quot;litong&quot;</span></span><br><span class="line">3) <span class="string">&quot;28&quot;</span></span><br><span class="line">127.0.0.1:6379&gt; hincrby user:01 <span class="built_in">id</span> 1 <span class="comment"># 可以根据某个filed进行自增</span></span><br><span class="line">(<span class="built_in">integer</span>) 2</span><br><span class="line">127.0.0.1:6379&gt; hincrby user:01 <span class="built_in">id</span> -10 <span class="comment"># 可以根据某个filed按步长进行自减</span></span><br><span class="line">(<span class="built_in">integer</span>) -8</span><br><span class="line">127.0.0.1:6379&gt; hincrby user:01 idx 1  <span class="comment"># 当filed不存在时，会自动set field并自增/自减</span></span><br><span class="line">(<span class="built_in">integer</span>) 1</span><br><span class="line">127.0.0.1:6379&gt; hget user:01 idx <span class="comment"># 查看idx字段值</span></span><br><span class="line"><span class="string">&quot;1&quot;</span></span><br><span class="line">127.0.0.1:6379&gt; hkeys user:01  <span class="comment"># 获取所有字段域field，时间复杂度O(n)</span></span><br><span class="line">1) <span class="string">&quot;id&quot;</span></span><br><span class="line">2) <span class="string">&quot;name&quot;</span></span><br><span class="line">3) <span class="string">&quot;age&quot;</span></span><br><span class="line">4) <span class="string">&quot;idx&quot;</span></span><br><span class="line">127.0.0.1:6379&gt; hvals user:01 <span class="comment"># 获取所有字段域field value，时间复杂度O(n)</span></span><br><span class="line">1) <span class="string">&quot;-8&quot;</span></span><br><span class="line">2) <span class="string">&quot;litong&quot;</span></span><br><span class="line">3) <span class="string">&quot;28&quot;</span></span><br><span class="line">4) <span class="string">&quot;1&quot;</span></span><br><span class="line">127.0.0.1:6379&gt; hgetall user:01 <span class="comment"># 获取所有字段域与字段值，时间复杂度O(n)</span></span><br><span class="line">1) <span class="string">&quot;id&quot;</span></span><br><span class="line">2) <span class="string">&quot;-8&quot;</span></span><br><span class="line">3) <span class="string">&quot;name&quot;</span></span><br><span class="line">4) <span class="string">&quot;litong&quot;</span></span><br><span class="line">5) <span class="string">&quot;age&quot;</span></span><br><span class="line">6) <span class="string">&quot;28&quot;</span></span><br><span class="line">7) <span class="string">&quot;idx&quot;</span></span><br><span class="line">8) <span class="string">&quot;1&quot;</span></span><br><span class="line">127.0.0.1:6379&gt; hlen user:01 <span class="comment"># 查看field个数</span></span><br><span class="line">(<span class="built_in">integer</span>) 4</span><br><span class="line">127.0.0.1:6379&gt; hexists user:01 idx  <span class="comment"># 判断idx这个field是否存在</span></span><br><span class="line">(<span class="built_in">integer</span>) 0</span><br><span class="line">127.0.0.1:6379&gt; hexists user:01 <span class="built_in">id</span> <span class="comment"># # 判断id这个field是否存在</span></span><br><span class="line">(<span class="built_in">integer</span>) 1</span><br><span class="line">127.0.0.1:6379&gt; hdel user:01 idx  <span class="comment"># 删除一个field</span></span><br><span class="line">(<span class="built_in">integer</span>) 1</span><br><span class="line">127.0.0.1:6379&gt; hget user:01 idx  <span class="comment"># 判断idx这个field是否存在</span></span><br><span class="line">(nil)</span><br><span class="line">127.0.0.1:6379&gt; hscan user:01 0 match <span class="string">&quot;*a*&quot;</span> <span class="comment"># 快速检索，获取包含有a字符的field</span></span><br><span class="line">1) <span class="string">&quot;0&quot;</span></span><br><span class="line">2) 1) <span class="string">&quot;name&quot;</span></span><br><span class="line">   2) <span class="string">&quot;litong&quot;</span></span><br><span class="line">   3) <span class="string">&quot;age&quot;</span></span><br><span class="line">   4) <span class="string">&quot;28&quot;</span></span><br></pre></td></tr></table></figure>

<h3 id="扩展说明-1"><a href="#扩展说明-1" class="headerlink" title="扩展说明"></a>扩展说明</h3><ol>
<li><p>大多数Hash命令的时间复杂度为O(1)，一些命令，比如<a target="_blank" rel="noopener" href="https://redis.io/commands/hkeys"><code>HKEYS</code></a>、<a target="_blank" rel="noopener" href="https://redis.io/commands/hvals"><code>HVALS</code></a>和<a target="_blank" rel="noopener" href="https://redis.io/commands/hgetall"><code>HGETALL</code></a>为O(n)，其中n为filed个数。</p>
</li>
<li><p>每个哈希理论上最多可以存储 4,294,967,295 (2^32 - 1) 个字段值对。实际上，取决于Redis服务器部署的 VM 上的总内存的限制。</p>
</li>
<li><p>应用场景：对象结构化数据</p>
<ol>
<li>购物车实现：Key为用户关联、field为购买商品ID、Value为购买数量信息</li>
<li>首页Banner广告实现：Key固定、field为广告ID、Value为广告详情信息</li>
<li>计数器分组：进行业务上的天、周计数，比如看广告多少次、消费多少笔领奖励等活动计数业务。</li>
</ol>
</li>
</ol>
<h2 id="List"><a href="#List" class="headerlink" title="List"></a>List</h2><p> List列表是用来存取多个有序的字符串，有序且可重复。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br></pre></td><td class="code"><pre><span class="line">127.0.0.1:6379&gt; LPUSH tasks 101   <span class="comment"># 存数据</span></span><br><span class="line">(<span class="built_in">integer</span>) 1</span><br><span class="line">127.0.0.1:6379&gt; LPUSH tasks 101   <span class="comment"># 重复存数据</span></span><br><span class="line">(<span class="built_in">integer</span>) 1</span><br><span class="line">127.0.01:6379&gt; lpush tasks 102   <span class="comment"># 继续存数据</span></span><br><span class="line">(<span class="built_in">integer</span>) 2</span><br><span class="line">127.0.0.1:6379&gt; rpush xxx a b c  <span class="comment"># 可以同时插入多个元素到列表</span></span><br><span class="line">(<span class="built_in">integer</span>) 3</span><br><span class="line">127.0.0.1:6379&gt; rpop tasks    <span class="comment"># 实现先进先出的队列</span></span><br><span class="line"><span class="string">&quot;101&quot;</span></span><br><span class="line">127.0.0.1:6379&gt; rpop tasks    <span class="comment"># 实现先进先出的队列，发现可重复存取</span></span><br><span class="line"><span class="string">&quot;101&quot;</span></span><br><span class="line">127.0.0.1:6379&gt; rpop tasks   <span class="comment"># 实现先进先出的队列</span></span><br><span class="line"><span class="string">&quot;102&quot;</span></span><br><span class="line">127.0.0.1:6379&gt; LPUSH tasks 101   <span class="comment"># 存数据</span></span><br><span class="line">(<span class="built_in">integer</span>) 1</span><br><span class="line">127.0.0.1:6379&gt; LPUSH tasks 102   <span class="comment"># 存数据</span></span><br><span class="line">(<span class="built_in">integer</span>) 2</span><br><span class="line">127.0.0.1:6379&gt; llen tasks  <span class="comment"># 查看list元素个数</span></span><br><span class="line">(<span class="built_in">integer</span>) 2</span><br><span class="line">127.0.0.1:6379&gt; lpop tasks  <span class="comment"># 实现先进后出的栈</span></span><br><span class="line"><span class="string">&quot;102&quot;</span></span><br><span class="line">127.0.0.1:6379&gt; lpop tasks  <span class="comment"># 实现先进后出的栈</span></span><br><span class="line"><span class="string">&quot;101&quot;</span></span><br><span class="line">127.0.0.1:6379&gt; LPUSH tasks 101</span><br><span class="line">(<span class="built_in">integer</span>) 1</span><br><span class="line">127.0.0.1:6379&gt; LPUSH tasks 102</span><br><span class="line">(<span class="built_in">integer</span>) 2</span><br><span class="line">127.0.0.1:6379&gt; LPUSH tasks 103</span><br><span class="line">(<span class="built_in">integer</span>) 3</span><br><span class="line">127.0.0.1:6379&gt; lrange tasks 0 -1 <span class="comment"># 按偏移量取出，-1表示最后一个元素</span></span><br><span class="line">1) <span class="string">&quot;103&quot;</span></span><br><span class="line">2) <span class="string">&quot;102&quot;</span></span><br><span class="line">3) <span class="string">&quot;101&quot;</span></span><br><span class="line">127.0.0.1:6379&gt; lindex tasks 1  <span class="comment"># 根据索引下标获取元素</span></span><br><span class="line"><span class="string">&quot;102&quot;</span></span><br><span class="line">127.0.0.1:6379&gt; ltrim tasks 1 -1  <span class="comment"># 保留区间元素，不在区间的元素将会删除</span></span><br><span class="line">OK</span><br><span class="line">127.0.0.1:6379&gt; lrange tasks 0 -1  </span><br><span class="line">1) <span class="string">&quot;102&quot;</span></span><br><span class="line">2) <span class="string">&quot;101&quot;</span></span><br><span class="line">127.0.0.1:6379&gt; rpush words hello  <span class="comment"># 右侧插入</span></span><br><span class="line">(<span class="built_in">integer</span>) 1</span><br><span class="line">127.0.0.1:6379&gt; rpush words what  <span class="comment"># 右侧插入</span></span><br><span class="line">(<span class="built_in">integer</span>) 2</span><br><span class="line">127.0.0.1:6379&gt; rpush words is   <span class="comment"># 右侧插入</span></span><br><span class="line">(<span class="built_in">integer</span>) 3</span><br><span class="line">127.0.0.1:6379&gt; rpush words your  <span class="comment"># 右侧插入</span></span><br><span class="line">(<span class="built_in">integer</span>) 4</span><br><span class="line">127.0.0.1:6379&gt; rpush words name  <span class="comment"># 右侧插入</span></span><br><span class="line">(<span class="built_in">integer</span>) 5</span><br><span class="line">127.0.0.1:6379&gt; rpush words what   <span class="comment"># 右侧插入</span></span><br><span class="line">(<span class="built_in">integer</span>) 6</span><br><span class="line">127.0.0.1:6379&gt; lrange words 0 -1</span><br><span class="line">1) <span class="string">&quot;hello&quot;</span></span><br><span class="line">2) <span class="string">&quot;what&quot;</span></span><br><span class="line">3) <span class="string">&quot;is&quot;</span></span><br><span class="line">4) <span class="string">&quot;your&quot;</span></span><br><span class="line">5) <span class="string">&quot;name&quot;</span></span><br><span class="line">6) <span class="string">&quot;what&quot;</span>  </span><br><span class="line">127.0.0.1:6379&gt; lrem words 0 hello  <span class="comment"># 移除hello的所有元素，这里的count等于0表示移除所有</span></span><br><span class="line">(<span class="built_in">integer</span>) 1  <span class="comment"># 当然count也可以小于0(从尾到头移除)，大于0(从头到尾移除)</span></span><br><span class="line">127.0.0.1:6379&gt; lrange words 0 -1</span><br><span class="line">1) <span class="string">&quot;what&quot;</span></span><br><span class="line">2) <span class="string">&quot;is&quot;</span></span><br><span class="line">3) <span class="string">&quot;your&quot;</span></span><br><span class="line">4) <span class="string">&quot;name&quot;</span></span><br><span class="line">5) <span class="string">&quot;what&quot;</span></span><br><span class="line">127.0.0.1:6379&gt; lmove tasks tasksOk LEFT LEFT <span class="comment"># 移除元素到新的列表中</span></span><br></pre></td></tr></table></figure>

<h3 id="扩展说明-2"><a href="#扩展说明-2" class="headerlink" title="扩展说明"></a>扩展说明</h3><ol>
<li><p>时间复杂度：访问列表和头部都是O(1), 大部分操作元素的命令是O(n)，例如<a target="_blank" rel="noopener" href="https://redis.io/commands/lpop/"><code>LPOP</code></a>, <a target="_blank" rel="noopener" href="https://redis.io/commands/lindex"><code>LINDEX</code></a>, <a target="_blank" rel="noopener" href="https://redis.io/commands/linsert"><code>LINSERT</code></a>, and <a target="_blank" rel="noopener" href="https://redis.io/commands/lset"><code>LSET</code></a>等命令。</p>
</li>
<li><p>每个哈希理论上最多可以存储 4,294,967,295 (2^32 - 1) 个字段值对。</p>
</li>
<li><p><a target="_blank" rel="noopener" href="https://redis.io/commands/blpop"><code>BLPOP</code></a>和<a target="_blank" rel="noopener" href="https://redis.io/commands/blmove"><code>BLMOVE</code></a>这是两个阻塞命令。</p>
</li>
<li><p>应用场景：</p>
<ol>
<li>消息队列：lpush(生产消息)+brpop(消费消息并阻塞)可实现阻塞队列。</li>
<li>最新列表：lpush+lrange可实现最新列表，朋友圈点赞列表、评论列表。</li>
</ol>
</li>
</ol>
<h2 id="Set"><a href="#Set" class="headerlink" title="Set"></a>Set</h2><p>  去重后的无序集合，且value都是字符串类型的。</p>
<h3 id="基本操作-2"><a href="#基本操作-2" class="headerlink" title="基本操作"></a>基本操作</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line">127.0.0.1:6379&gt; sadd skill:litong java  <span class="comment"># litong的skill列表</span></span><br><span class="line">(<span class="built_in">integer</span>) 1</span><br><span class="line">127.0.0.1:6379&gt; sismember skill:litong java <span class="comment"># 检测litong是具备Java语言技能</span></span><br><span class="line">(<span class="built_in">integer</span>) 1</span><br><span class="line">127.0.0.1:6379&gt; sismember skill:litong go <span class="comment"># 检测litong是具备Go语言技能</span></span><br><span class="line">(<span class="built_in">integer</span>) 0</span><br><span class="line">127.0.0.1:6379&gt; sadd skill:litong redis <span class="comment"># 再添加一个redis的技能</span></span><br><span class="line">(<span class="built_in">integer</span>) 0</span><br><span class="line">127.0.0.1:6379&gt; sadd skill:litong java <span class="comment"># 再重复添加一个java的技能</span></span><br><span class="line">(<span class="built_in">integer</span>) 0</span><br><span class="line">127.0.0.1:6379&gt; scard skill:litong <span class="comment"># 查看litong的技能数，实际上java已经去重了，比如统计UV</span></span><br><span class="line">(<span class="built_in">integer</span>) 2</span><br><span class="line">127.0.0.1:6379&gt; sadd skill:lisi redis  <span class="comment"># 添加lisi的技能列表，并默认给个redis技能</span></span><br><span class="line">(<span class="built_in">integer</span>) 1</span><br><span class="line">127.0.0.1:6379&gt; sinter skill:lisi skill:litong <span class="comment"># 取交集，litong和lisi共同的技能，常用于共同好友</span></span><br><span class="line">1) <span class="string">&quot;redis&quot;</span></span><br><span class="line">127.0.0.1:6379&gt; sdiff skill:litong skill:lisi <span class="comment"># 取差集</span></span><br><span class="line">1) <span class="string">&quot;java&quot;</span></span><br><span class="line">127.0.0.1:6379&gt; sdiff skill:lisi skill:litong <span class="comment"># 取差集</span></span><br><span class="line">(empty list or <span class="built_in">set</span>)</span><br><span class="line">127.0.0.1:6379&gt; sadd skill:lisi webpack  <span class="comment"># 给lisi添加webpack技能</span></span><br><span class="line">(<span class="built_in">integer</span>) 1</span><br><span class="line">127.0.0.1:6379&gt; sunion skill:litong skill:lisi  <span class="comment"># 取并集</span></span><br><span class="line">1) <span class="string">&quot;webpack&quot;</span></span><br><span class="line">2) <span class="string">&quot;java&quot;</span></span><br><span class="line">3) <span class="string">&quot;redis&quot;</span></span><br><span class="line">127.0.0.1:6379&gt; srandmember skill:litong 1  <span class="comment"># 随机取一个元素</span></span><br><span class="line">1) <span class="string">&quot;redis&quot;</span></span><br><span class="line">127.0.0.1:6379&gt; srem skill:litong java  <span class="comment"># 删除一个元素</span></span><br><span class="line">(<span class="built_in">integer</span>) 1</span><br><span class="line">127.0.0.1:6379&gt; sismember skill:litong java <span class="comment"># 已删除成功了</span></span><br><span class="line">(<span class="built_in">integer</span>) 0</span><br></pre></td></tr></table></figure>

<h3 id="扩展说明-3"><a href="#扩展说明-3" class="headerlink" title="扩展说明"></a>扩展说明</h3><ol>
<li><p>大多数Set命令的时间复杂度为O(1)，一些命令，比如<a target="_blank" rel="noopener" href="https://redis.io/commands/smembers"><code>SMEMBERS</code></a>(可替代为<a target="_blank" rel="noopener" href="https://redis.io/commands/sscan"><code>SSCAN</code></a>)为O(n)，其中n为filed个数。</p>
</li>
<li><p>Redis Set集合的最大大小为 2^32 - 1 (4,294,967,295) 个成员。</p>
</li>
<li><p>海量数据下，判断一个元素是否在集合中，考虑内存成本的话，对精度要求不高，请采用<a target="_blank" rel="noopener" href="https://redis.io/docs/stack/bloom">布隆过滤器</a>方案替代。</p>
</li>
<li><p>应用场景：集合运算(交集、并集、差集)、去重</p>
<ol>
<li>交集数据：共同好友、共同关注、好友数量</li>
<li>去重应用：UV统计(百万级UV用HyperLogLog)、中奖用户存取、点赞</li>
<li>随机业务：首页广告，随机取出若干广告</li>
</ol>
</li>
</ol>
<h2 id="ZSet"><a href="#ZSet" class="headerlink" title="ZSet"></a>ZSet</h2><p>  ZSet有序集合是按相关分数排序的唯一字符串集合。上述Set的增强(排序)版。当多个元素具有相同的分数时，元素按照字典排序。</p>
<h3 id="基本操作-3"><a href="#基本操作-3" class="headerlink" title="基本操作"></a>基本操作</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">127.0.0.1:6379&gt; zadd student 82 lisi  <span class="comment"># 添加学生成绩单</span></span><br><span class="line">(<span class="built_in">integer</span>) 1</span><br><span class="line">127.0.0.1:6379&gt; zadd student 53 zhangsan  <span class="comment"># 添加学生成绩单</span></span><br><span class="line">(<span class="built_in">integer</span>) 1</span><br><span class="line">127.0.0.1:6379&gt; zadd student 100 litong  <span class="comment"># 添加学生成绩单</span></span><br><span class="line">(<span class="built_in">integer</span>) 1</span><br><span class="line">127.0.0.1:6379&gt; zadd student 61 wangwu  <span class="comment"># 添加学生成绩单</span></span><br><span class="line">(<span class="built_in">integer</span>) 1</span><br><span class="line">127.0.0.1:6379&gt; zadd student 77 zhaoliu  <span class="comment"># 添加学生成绩单</span></span><br><span class="line">(<span class="built_in">integer</span>) 1</span><br><span class="line">127.0.0.1:6379&gt; zrevrank student litong  <span class="comment"># 降序列表中，查看litong的分数所在的位置排名</span></span><br><span class="line">(<span class="built_in">integer</span>) 0</span><br><span class="line">127.0.0.1:6379&gt; zrank student litong  <span class="comment"># 升序列表中，查看litong的分数所在的排名</span></span><br><span class="line">(<span class="built_in">integer</span>) 4</span><br><span class="line">127.0.0.1:6379&gt; zrange student 0 2  <span class="comment"># 查看分数倒数的三位活宝</span></span><br><span class="line">1) <span class="string">&quot;zhangsan&quot;</span></span><br><span class="line">2) <span class="string">&quot;wangwu&quot;</span></span><br><span class="line">3) <span class="string">&quot;zhaoliu&quot;</span></span><br><span class="line">127.0.0.1:6379&gt; ZREVRANGE student 0 2 <span class="comment"># 查看分数前三名</span></span><br><span class="line">1) <span class="string">&quot;litong&quot;</span></span><br><span class="line">2) <span class="string">&quot;lisi&quot;</span></span><br><span class="line">3) <span class="string">&quot;zhaoliu&quot;</span></span><br></pre></td></tr></table></figure>

<h3 id="扩展说明-4"><a href="#扩展说明-4" class="headerlink" title="扩展说明"></a>扩展说明</h3><ol>
<li><p>大多数ZSet命令的时间复杂度为O(log(n))，一些命令，比如 <a target="_blank" rel="noopener" href="https://redis.io/commands/zrange"><code>ZRANGE</code></a>在大数量下的时间复杂度可能为O (log (n) + m)，其中m为返回的结果数。</p>
</li>
<li><p>Redis ZSet有序集合的最大大小为 2^32 - 1 (4,294,967,295) 个成员。</p>
</li>
<li><p>应用场景：</p>
<ol>
<li>排序应用：动态实现排序功能，如Top榜单、联系人列表排序。</li>
<li>限流应用：可以使用排序集来构建滑动窗口速率限制器，以防止过多的API请求。</li>
</ol>
</li>
</ol>
<h2 id="BitMap"><a href="#BitMap" class="headerlink" title="BitMap"></a>BitMap</h2><p>  Bitmap位图是值为0或1的集合。可以节省大量的内存空间。基于String类型的按位操作，最大支持232位。</p>
<h3 id="基本操作-4"><a href="#基本操作-4" class="headerlink" title="基本操作"></a>基本操作</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">127.0.0.1:6379&gt; setbit user:sign:101:2301 6 1  <span class="comment"># 101用户23年1月7的进行签到，偏移从0开始</span></span><br><span class="line">(<span class="built_in">integer</span>) 0</span><br><span class="line">127.0.0.1:6379&gt; getbit user:sign:101:2301 6  <span class="comment"># 判断101用户23年1月7的是否已签到</span></span><br><span class="line">(<span class="built_in">integer</span>) 1 </span><br><span class="line">127.0.0.1:6379&gt; getbit user:sign:101:2301 7  <span class="comment"># 判断101用户23年1月8的是否已签到</span></span><br><span class="line">(<span class="built_in">integer</span>) 0</span><br><span class="line">127.0.0.1:6379&gt; setbit user:sign:101:2301 10 2  <span class="comment"># 只可以是0或1，否则报错</span></span><br><span class="line">(error) ERR bit is not an <span class="built_in">integer</span> or out of range</span><br><span class="line">127.0.0.1:6379&gt; setbit user:sign:101:2301 10 -2 <span class="comment"># 只可以是0或1，否则报错</span></span><br><span class="line">(error) ERR bit is not an <span class="built_in">integer</span> or out of range</span><br><span class="line">127.0.0.1:6379&gt; setbit user:sign:101:2301 12 1 <span class="comment"># 101用户23年1月13的进行签到</span></span><br><span class="line">(<span class="built_in">integer</span>) 0</span><br><span class="line">127.0.0.1:6379&gt; bitcount user:sign:101:2301  <span class="comment"># 统计23年1月总共签到的次数</span></span><br><span class="line">(<span class="built_in">integer</span>) 2</span><br><span class="line">127.0.0.1:6379&gt; bitpos user:sign:101:2301 1 <span class="comment"># 获取23年1月首次签到的日期</span></span><br><span class="line">(<span class="built_in">integer</span>) 6</span><br><span class="line">127.0.0.1:6379&gt; bitfield user:sign:101:2301 get u11 0 <span class="comment"># 获取取23年1月前11天的签到数据</span></span><br><span class="line">1) 16</span><br></pre></td></tr></table></figure>

<h3 id="扩展说明-5"><a href="#扩展说明-5" class="headerlink" title="扩展说明"></a>扩展说明</h3><ol>
<li><p><a target="_blank" rel="noopener" href="https://redis.io/commands/setbit"><code>SETBIT</code></a>和<a target="_blank" rel="noopener" href="https://redis.io/commands/getbit"><code>GETBIT</code></a>是 O(1)。 <a target="_blank" rel="noopener" href="https://redis.io/commands/bitop"><code>BITOP</code></a>是 O(n)，其中<em>n</em>是比较中最长字符串的长度。</p>
</li>
<li><p>应用场景：</p>
<ol>
<li>签到数据：这部分数据具有时效性，用数据库存量很大，不太适合。</li>
<li>权限以及标志位：对象权限，每位代表一个特定的权限，例如Linux权限符、实时统计用户在线状态。</li>
</ol>
</li>
</ol>
<h2 id="GEO"><a href="#GEO" class="headerlink" title="GEO"></a>GEO</h2><p>  Redis地理空间索引让您可以存储坐标并进行搜索。此数据结构可用于查找给定半径或边界框内的附近点。Redis3.2.X以上版本才支持。</p>
<h3 id="基本操作-5"><a href="#基本操作-5" class="headerlink" title="基本操作"></a>基本操作</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">127.0.0.1:6379&gt; geoadd jingdian 121.497204 31.243453 waitan <span class="comment"># 添加景点外滩的经纬度</span></span><br><span class="line">(<span class="built_in">integer</span>) 1</span><br><span class="line">127.0.0.1:6379&gt; geoadd jingdian 121.506379 31.245414 dongfangmz  <span class="comment"># 添加景点东方明珠电视塔的经纬度</span></span><br><span class="line">(<span class="built_in">integer</span>) 1</span><br><span class="line">127.0.0.1:6379&gt; geoadd jingdian 121.506379 31.245414 disney  <span class="comment"># 添加景点迪士尼的经纬度</span></span><br><span class="line">(<span class="built_in">integer</span>) 1</span><br><span class="line">127.0.0.1:6379&gt;  geosearch jingdian fromlonlat 121.508506 31.238794 byradius 10 km withdist  <span class="comment"># 上海汤臣一品附近10km的景点</span></span><br></pre></td></tr></table></figure>

<h3 id="扩展说明-6"><a href="#扩展说明-6" class="headerlink" title="扩展说明"></a>扩展说明</h3><ol>
<li><p><a target="_blank" rel="noopener" href="https://redis.io/commands/geoadd"><code>GEOADD</code></a>是O(log(N))。 <a target="_blank" rel="noopener" href="https://redis.io/commands/geosearch"><code>GEOSEARCH</code></a>是O(N+log(M)),其中 N 是作为过滤器提供的形状周围的网格对齐边界框区域中的元素数，M 是形状内部的项目数。</p>
</li>
<li><p>应用场景：</p>
<ol>
<li>附近搜索场景：打车、外卖、景点、酒店、公交站点等LBS服务。</li>
</ol>
</li>
</ol>
<h1 id="过期与淘汰"><a href="#过期与淘汰" class="headerlink" title="过期与淘汰"></a>过期与淘汰</h1><h2 id="过期删除机制"><a href="#过期删除机制" class="headerlink" title="过期删除机制"></a>过期删除机制</h2><p>redis可以通过expire(秒)、pexpire(毫秒)、expireat(指定时间戳，精确到秒)、pexpireat(指定时间戳，精确到毫秒)4个命令对Key设置有效期。ttl结束key被删除的过程就是过期删除机制。</p>
<p>过期删除机制有三种：定时删除、惰性删除、定期删除</p>
<p>Redis默认选用惰性删除+定期删除来处理。执行流程如下：</p>
<ol>
<li><p>默认每秒10次(redis.conf中hz来配置)随机抽取20(代码写死)个key进行过期检查。</p>
</li>
<li><p>如果本轮检查的已过期 key 的数量，超过 5 个（20*25%），也就是「已过期 key 的数量」占比「随机抽取 key 的数量」大于 25%，则进行新一轮20个key检查与删除。</p>
</li>
<li><p>如果已过期的 key 比例小于 25%，则停止继续删除过期 key，然后等待下一轮再检查。</p>
</li>
</ol>
<p>Redis 为了保证定期删除不会出现循环过度，导致线程卡死现象，为此增加了定期删除循环流程的时间上限，默认不会超过 25ms。</p>
<h3 id="定时删除"><a href="#定时删除" class="headerlink" title="定时删除"></a>定时删除</h3><p>设置有效期同时会创建定时时间，当时间达到，由事件处理器自动执行删除key的操作。</p>
<p>优点：实时删除对内存友好。</p>
<p>缺点：当过期key较多的情况下，事件任务执行会增多，对CPU不友好。</p>
<h3 id="惰性删除"><a href="#惰性删除" class="headerlink" title="惰性删除"></a>惰性删除</h3><p>不主动删除过期key，每次访问该过期key时，检测key是否过期，如果过期那么就删除。</p>
<p>当然删除可以是异步删除，也可以是同步删除。</p>
<p>优点：占用较少的系统资源，对CPU较友好。</p>
<p>缺点：当一个key已经过期了，而这个key一直保留在库里，不会被外部访问，内存暂时也不会释放，造成一定的内存浪费。对内存不友好。</p>
<h3 id="定期删除"><a href="#定期删除" class="headerlink" title="定期删除"></a>定期删除</h3><p>每隔一段时间随机从库里取一堆key进行检查，并删除其中过期的key。</p>
<p>优点：折中处理，通过控制操作时长和频率，既能减少操作对CPU的影响，又可以减少过期key对内存的无效占用。</p>
<p>缺点：内存清理方面没有定时删除效果好，同时没有惰性删除使用的系统资源少。难以确定删除操作执行的时长和频率。如果执行的太频繁，定期删除策略变得和定时删除策略一样，对CPU不友好；如果执行的太少，那又和惰性删除一样了，过期 key 占用的内存不会及时得到释放。</p>
<h2 id="内存淘汰策略"><a href="#内存淘汰策略" class="headerlink" title="内存淘汰策略"></a>内存淘汰策略</h2><h3 id="八种策略"><a href="#八种策略" class="headerlink" title="八种策略"></a>八种策略</h3><p>大家有没有想过，当redis最大内存不足以支撑现有的数据存取，怎么办？</p>
<p>Redis存放数据是有最大内存限制的。在redis.conf配置文件下：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 内存大小，64系统默认为0不限制内存大小，而32位隐式限制内存大小为3G(32位最大内存为4G，剩下1G留给其他资源)</span></span><br><span class="line">maxmemory 100mb  </span><br></pre></td></tr></table></figure>

<p>根据我们应用程序的请求模式，要选择合适的内存淘汰机制。</p>
<p>通过maxmemory-policy可以查看与配置内存淘汰策略。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">127.0.0.1:6379&gt; config get maxmemory-policy <span class="comment"># 查看当前redis的内存淘汰策略</span></span><br><span class="line">1) <span class="string">&quot;maxmemory-policy&quot;</span></span><br><span class="line">2) <span class="string">&quot;noeviction&quot;</span></span><br><span class="line">127.0.0.1:6379&gt; config <span class="built_in">set</span> maxmemory-policy 策略名 <span class="comment"># 即时生效，重启后失效，可在配置文件里修改</span></span><br></pre></td></tr></table></figure>

<p>Redis提供了8种内存淘汰策略，具体如下：</p>
<ul>
<li><strong>noeviction</strong>: 当达到内存限制时，不会淘汰旧值，读删操作正常，但写操作会触发OOM。当使用复制模式时，这适用于主节点。Redis3.0之后默认的内存淘汰策略。</li>
<li><strong>allkeys-lru</strong>: 保留最近使用的Key，删除最久未使用的Key。</li>
<li><strong>allkeys-lfu</strong>: 保留使用频率高的Key， 删除最不经常使用的Key。Redis4.0后新增的策略。</li>
<li><strong>volatile-lru</strong>: 移除设置有效期且久未的Key。Redis3.0之前默认的内存淘汰策略。</li>
<li><strong>volatile-lfu</strong>: 移除设置有效期且使用频率最少的Key。Redis4.0新增的策略。</li>
<li><strong>allkeys-random</strong>: 在所有key中随机淘汰。</li>
<li><strong>volatile-random</strong>: 在设置有效期所有key中随机淘汰。</li>
<li><strong>volatile-ttl</strong>: 优先淘汰ttl更小的key。</li>
</ul>
<h3 id="近LRU算法"><a href="#近LRU算法" class="headerlink" title="近LRU算法"></a>近LRU算法</h3><p><strong>LRU</strong> 全称是 Least Recently Used 翻译为<strong>最近最少使用</strong>，会选择淘汰最近最少使用的数据。</p>
<p>传统 LRU 算法的实现是基于「链表」结构，链表中的元素按照操作顺序从前往后排列，最新操作的键会被移动到表头，当需要内存淘汰时，只需要删除链表尾部的元素即可，因为链表尾部的元素就代表最久未被使用的元素。</p>
<p>我们Java通过LinkedHashMap来实现传统的LRU：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.LinkedHashMap;</span><br><span class="line"><span class="keyword">import</span> java.util.Map;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@className</span>: com.test.LRUCache</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@description</span>:</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span>: tong.li</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@createTime</span>: 2023/1/8 10:46</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@copyright</span>: Tongcheng-Elong Holdings Limited</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LRUCache</span> <span class="keyword">extends</span> <span class="title class_">LinkedHashMap</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 内存容量</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> capacity;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 构造</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> capacity</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">LRUCache</span><span class="params">(<span class="type">int</span> capacity)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(capacity);</span><br><span class="line">        <span class="built_in">this</span>.capacity = capacity;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="type">boolean</span> <span class="title function_">removeEldestEntry</span><span class="params">(Map.Entry eldest)</span> &#123;</span><br><span class="line">        <span class="comment">// 默认策略是永远返回false，即表示超过容量后不做任何处理</span></span><br><span class="line">        <span class="comment">// return super.removeEldestEntry(eldest);</span></span><br><span class="line">        <span class="comment">// 当已有的数据超过现有容量时，那么移除尾部的元素</span></span><br><span class="line">        <span class="keyword">return</span> size() &gt; capacity;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>Redis 并没有使用这样的方式实现 LRU 算法，因为传统的 LRU 算法存在两个问题：</p>
<ul>
<li>需要用链表管理所有的缓存数据，这会带来额外的空间开销；</li>
<li>当有数据被访问时，需要在链表上把该数据移动到头端，如果有大量数据被访问，就会带来很多链表移动操作，会很耗时，进而会降低 Redis 缓存性能。</li>
</ul>
<p>Redis 实现的是一种<strong>近似 LRU 算法</strong>，目的是为了更好的节约内存，它的<strong>实现方式是在 Redis 的对象结构体中添加一个额外的字段，用于记录此数据的最后一次访问时间</strong>。</p>
<p>当 Redis 进行内存淘汰时，会使用<strong>随机采样的方式来淘汰数据</strong>，它是随机取 5 个值（此值可配置），然后<strong>淘汰最久没有使用的那个</strong>。</p>
<p>Redis 实现的 LRU 算法的优点：</p>
<ul>
<li>不用为所有的数据维护一个大链表，节省了空间占用；</li>
<li>不用在每次数据访问时都移动链表项，提升了缓存的性能；</li>
</ul>
<p>不过Redis的LRU也有缺点：<strong>无法解决缓存污染问题</strong>，比如应用一次读取了大量的数据，而这些数据只会被读取这一次，那么这些数据会留存在 Redis 缓存中很长一段时间，造成缓存污染。因此Redis4.0之后引入了LFU算法。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">maxmemory-samples 5  <span class="comment"># Redis LRU算法不太精确，可以设置样本个数来进行检测以达到精确</span></span><br></pre></td></tr></table></figure>

<h3 id="LFU算法"><a href="#LFU算法" class="headerlink" title="LFU算法"></a>LFU算法</h3><p>LFU 全称是 Least Frequently Used 翻译为<strong>最近最不常用</strong>，LFU算法是根据数据访问次数来淘汰数据的。</p>
<p>所以， LFU 算法会记录每个数据的访问次数。当一个数据被再次访问时，就会增加该数据的访问次数。这样就解决了偶尔被访问一次之后，数据留存在缓存中很长一段时间的问题，相比于 LRU 算法也更合理一些。</p>
<p>LFU相关的配置信息：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">lfu-log-factor 10 <span class="comment"># 可以调整计数器counter的增长速度，lfu-log-factor越大，counter增长的越慢。越小淘汰越慢</span></span><br><span class="line">lfu-decay-time 1 <span class="comment"># 是一个以分钟为单位的数值，可以调整counter的减少速度</span></span><br></pre></td></tr></table></figure>

<h1 id="应用缓存"><a href="#应用缓存" class="headerlink" title="应用缓存"></a>应用缓存</h1><h2 id="缓存异常"><a href="#缓存异常" class="headerlink" title="缓存异常"></a>缓存异常</h2><p>我们做应用程序的时候，业务数据一般持久化到DB层。</p>
<p>因DB的数据存储是基于文件磁盘的，读写速度没用Redis等内存数据库快，因此，我们一般会加缓存层来提升整个系统性能。</p>
<p>值得注意的是，在缓存命中的过程中并不是一帆风顺的，可能会面临一些缓存异常与一致性的问题。</p>
<h3 id="缓存预热"><a href="#缓存预热" class="headerlink" title="缓存预热"></a>缓存预热</h3><p>缓存预热是指系统上线后，提前将热点数据加载到缓存系统中，避免刚上线用户有太多的请求直接访问到DB。</p>
<p>通常数据量不大的情况下，可以不进行预热。数据量很大的时候，一定要保证热点数据提前加载的系统中。预热方式有两种：</p>
<ol>
<li><p>手动预热：首页广告数据，防止运营人员配置错误，需要配置手动刷缓存进行生效，适用于热点数据经常变化的情况。</p>
</li>
<li><p>系统预热：城市列表数据、三方公钥数据，适用于热点数据不经常更新的情况。</p>
</li>
</ol>
<h3 id="缓存雪崩"><a href="#缓存雪崩" class="headerlink" title="缓存雪崩"></a>缓存雪崩</h3><p>故障描述：大量数据在某个时间点同时失效或Redis服务故障宕机情况下，大量用户请求直接到DB层，导致DB层压力巨大而到崩溃，这种一系列的连锁反应就是缓存雪崩。</p>
<p>针对不同情况解决方案也不同：</p>
<ol>
<li><p>大量数据同时过期：合理设置过期时间(高频热点数据可设置永久缓存)、互斥锁、二级缓存(双key策略)、后台异步更新缓存。</p>
</li>
<li><p>Redis服务器故障宕机：构建高可用的集群、服务熔断、请求限流、订阅MySQL BinLog同步数据。</p>
</li>
</ol>
<h3 id="缓存击穿"><a href="#缓存击穿" class="headerlink" title="缓存击穿"></a>缓存击穿</h3><p>故障描述：缓存击穿和缓存雪崩很类似，可以认为击穿是雪崩的特殊情况。某些热点数据经常会被频繁的访问，比如：秒杀。若这些热点数据过期了，还有大量请求在访问，瞬间情况下，无法从缓存中读取，这些请求就直接到DB层了，DB层也很容易被搞垮。</p>
<p>解决方案：互斥锁、由后台异步更新缓存(Redission的电子狗)。</p>
<h3 id="缓存穿透"><a href="#缓存穿透" class="headerlink" title="缓存穿透"></a>缓存穿透</h3><p>故障描述：访问的数据既缓存层和DB层无法命中，导致资源被耗用的情况。若有大量请求时，缓存层和DB层都有巨大的压力，应用场景下：恶意攻击、业务系统漏洞。</p>
<p>解决方案：非法请求的IP限制，缓存空值或默认值、使用布隆过滤器快速判断数据是否存在、代码Review。</p>
<h2 id="缓存一致性"><a href="#缓存一致性" class="headerlink" title="缓存一致性"></a>缓存一致性</h2><p>因缓存层和DB层都存有双份数据，那么必定有一致性的问题。</p>
<ul>
<li><p>强一致性：时刻保证DB和缓存数据一致。</p>
</li>
<li><p>弱一致性：可以短期容忍DB和缓存数据不一致，一般都是异步操作缓存</p>
</li>
<li><p>最终一致性：特殊的弱一致性，通过某种机制最终达到DB和缓存数据的一致性。</p>
</li>
</ul>
<p>为了解决缓存一致性问题，我们分为以下几种方案：</p>
<ul>
<li><p>先更新DB，后更新缓存：双写(概率高)和读写(概率较低)都会出现一致性问题。PASS。</p>
</li>
<li><p>先更新缓存，后更新DB：双写(概率高)和读写(概率较低)都会出现一致性问题。</p>
</li>
<li><p>先更新DB，后删除缓存：双写场景下几乎不出现，读写场景(小概率)会出现一致性问题。可以考虑。</p>
</li>
<li><p>先删除缓存，后更新DB：双写(几乎不出现)和读写(概率较高)都会出现一致性问题。</p>
</li>
</ul>
<p>这四种方式都有可能存在缓存数据和DB的不一致性，第三种出现这个问题的概率不太高，因为缓存的写入通常要远远快于数据库的写入，所以我们一般选择第三种+过期时间来达到缓存的最终一致性。</p>
<p>若我们业务对缓存的命中率很看重的话，我们也可以采用第一种方案+互斥锁+过期时间。</p>
<p>针对第4种情况，我们可以采用延迟双删来保证，再实现的过程中，需要sleep，性能不好，一般不用。</p>
<p>额外的兜底方案达到最终一致性：重试机制(MQ)、订阅MySQL BinLog，再操作缓存。</p>
<h1 id="高可用"><a href="#高可用" class="headerlink" title="高可用"></a>高可用</h1><p>之前文章写过，请移步<a target="_blank" rel="noopener" href="https://ltalex.gitee.io/2020/04/01/%E4%BA%86%E8%A7%A3%E5%B8%B8%E8%A7%81%E7%9A%84Redis%E9%9B%86%E7%BE%A4%E6%96%B9%E5%BC%8F/">这里</a>。</p>
<h1 id="持久化"><a href="#持久化" class="headerlink" title="持久化"></a>持久化</h1><p>之前文章写过，请移步<a target="_blank" rel="noopener" href="https://ltalex.gitee.io/2020/04/17/Redis%E6%8C%81%E4%B9%85%E5%8C%96%E6%9C%BA%E5%88%B6/">这里</a>。</p>
<h1 id="内部实现"><a href="#内部实现" class="headerlink" title="内部实现"></a>内部实现</h1><p>  Redis是基于C语言内部实现。不同数据类型采用合理的数据结构，大大的提升了数据的读写性能。<br>  <img src="https://ltyeamin.github.io/imgs/2023/01/9fa26a74965efbf0f56b707a03bb9b7f.png" alt="数据结构实现"></p>
<h2 id="SDS简单动态字符串"><a href="#SDS简单动态字符串" class="headerlink" title="SDS简单动态字符串"></a>SDS简单动态字符串</h2><p>  简单动态字符串(Simple Dynamic Strings, SDS)是Redis为了弥补C语言原生字符串(字符数组)的缺陷而实现的一套字符串类型。<br>  具体实现源代码文件为<a target="_blank" rel="noopener" href="https://github.com/antirez/sds/blob/master/sds.c">sds.c</a>。 C语言标准库的字符串本身的缺陷有以下几点：</p>
<ol>
<li>字符数组存储，指针只会指向数组的起始位置，而数组的结尾位置由字符“\0”表示，也就意味着只能存储文本数据，不可存储带有该字符的二进制数据。</li>
<li>无字符串的元数据，C语言是面向过程语言，要获取一个C语言字符串的长度，需要遍历完整个数组才可以实现，那么时间复杂度O(n)就相比较高了。</li>
<li>不安全，也无法动态扩容，C语言内存空间(分配和回收)需要我们手动控制，分配太大了容易造成内存浪费，分配小了容易引起内存(缓冲区)溢出。</li>
</ol>
<p>上述分析了C语言标准库字符串的缺陷，那么SDS有优点，也有部分缺点，我们下来作出分析。<br>优点：</p>
<ol>
<li>使用比较简单，可扩容，功能强大：代码简洁，</li>
<li>二进制安全：可存储任意形式的数据。</li>
<li>计算效率更高：获取字符串长度只需要访问元数据即可。追加函数数据使用零拷贝。</li>
<li>与C语言字符串函数兼容：可以将SDS字符串传递给为C函数设计的函数，而无需访问结构成员或调用函数。</li>
<li>节省内存空间。</li>
</ol>
<p>缺点：</p>
<ol>
<li>若SDS字符串在不同域中共享，更新该串时会修改所有引用，处理不好可能会导致内存泄露，所以一般使用SDS字符串时，我们一般封装到结构体中。</li>
<li>许多SDS函数调用后返回新的字符串，也有意味着每调用一次都会单独申请内存空间，大量使用会导致内存占用很高。</li>
</ol>
<p>SDS的数据结构在Redis 3.2中有较大的改动，3.2版本以前的SDS数据结构定义相较简单，源码如下：</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * sds简单动态字符串的结构体</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">sdshdr</span> &#123;</span></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 字符串大小，即buf[]已使用的数组长度</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="type">int</span> len;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 剩余可使用的空间，即buf[]未使用的数组长度</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="type">int</span> <span class="built_in">free</span>;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 字符串数据实际保存在字符数组buf[]</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="type">char</span> buf[];</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>上述结构体在内存中的布局为：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">+------------+------------------------+-----------+---------------\</span><br><span class="line">| Len | Free | H E L L O W O R L D \n | Null term |  Free space   \</span><br><span class="line">+------------+------------------------+-----------+---------------\</span><br><span class="line">|</span><br><span class="line">`-&gt; Pointer returned to the user.</span><br></pre></td></tr></table></figure>

<p>但是在Redis 3.2之后，内部结构体实现较之前大有不同：</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Redis 3.2之后sds简单动态字符串的结构体</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">sdshdr</span> &#123;</span></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 字符串大小，即buf[]已使用的数组长度</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="type">int</span> len;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 分配给字符数组的空间长度，可以通过alloc-len计算出剩余的空间大小(原先的free)</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="type">int</span> alloc;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * sds子类型，目的是为了压缩空间</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="type">char</span> flags;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 字符串数据实际保存在字符数组buf[]</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="type">char</span> buf[];</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>sds有5种不同的sds子类型：sdshdr5、sdshdr8、sdshdr16、sdshdr32和sdshdr64。 具体<a target="_blank" rel="noopener" href="https://github.com/antirez/sds/blob/master/sds.h">源代码</a>结构体的实现：</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* Note: sdshdr5 is never used, we just access the flags byte directly.</span></span><br><span class="line"><span class="comment"> * However is here to document the layout of type 5 SDS strings. */</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> __<span class="title">attribute__</span> ((__<span class="title">packed__</span>)) <span class="title">sdshdr5</span> &#123;</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">char</span> flags; <span class="comment">/* 3 lsb of type, and 5 msb of string length */</span></span><br><span class="line">    <span class="type">char</span> buf[];</span><br><span class="line">&#125;;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> __<span class="title">attribute__</span> ((__<span class="title">packed__</span>)) <span class="title">sdshdr8</span> &#123;</span></span><br><span class="line">    <span class="type">uint8_t</span> len; <span class="comment">/* used */</span></span><br><span class="line">    <span class="type">uint8_t</span> alloc; <span class="comment">/* excluding the header and null terminator */</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">char</span> flags; <span class="comment">/* 3 lsb of type, 5 unused bits */</span></span><br><span class="line">    <span class="type">char</span> buf[];</span><br><span class="line">&#125;;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> __<span class="title">attribute__</span> ((__<span class="title">packed__</span>)) <span class="title">sdshdr16</span> &#123;</span></span><br><span class="line">    <span class="type">uint16_t</span> len; <span class="comment">/* used */</span></span><br><span class="line">    <span class="type">uint16_t</span> alloc; <span class="comment">/* excluding the header and null terminator */</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">char</span> flags; <span class="comment">/* 3 lsb of type, 5 unused bits */</span></span><br><span class="line">    <span class="type">char</span> buf[];</span><br><span class="line">&#125;;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> __<span class="title">attribute__</span> ((__<span class="title">packed__</span>)) <span class="title">sdshdr32</span> &#123;</span></span><br><span class="line">    <span class="type">uint32_t</span> len; <span class="comment">/* used */</span></span><br><span class="line">    <span class="type">uint32_t</span> alloc; <span class="comment">/* excluding the header and null terminator */</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">char</span> flags; <span class="comment">/* 3 lsb of type, 5 unused bits */</span></span><br><span class="line">    <span class="type">char</span> buf[];</span><br><span class="line">&#125;;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> __<span class="title">attribute__</span> ((__<span class="title">packed__</span>)) <span class="title">sdshdr64</span> &#123;</span></span><br><span class="line">    <span class="type">uint64_t</span> len; <span class="comment">/* used */</span></span><br><span class="line">    <span class="type">uint64_t</span> alloc; <span class="comment">/* excluding the header and null terminator */</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">char</span> flags; <span class="comment">/* 3 lsb of type, 5 unused bits */</span></span><br><span class="line">    <span class="type">char</span> buf[];</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>这5种类型的主要区别就在于它们各自数据结构中的len和alloc成员变量的数据类型不同。<br>除了设计不同类型的结构体，Redis 在编程上还使用了专门的编译优化来节省内存空间，即在 struct 声明了 <strong>attribute</strong> ((packed)) ，<br>它的作用是：告诉编译器取消结构体在编译过程中的优化对齐，按照实际占用字节数进行对齐。 默认sdshdr16使用2字节对齐。</p>
<p>接下来，我们说一下sds的内部扩容机制：</p>
<ol>
<li>当字符串的长度小于1MB时，每次扩容都是加倍现有的空间，即2倍的newlen</li>
<li>如果字符串长度超过1MB时，每次扩容时只会扩展 1MB的空间，即newlen+1MB。</li>
<li>以下是<a target="_blank" rel="noopener" href="https://github.com/antirez/sds/blob/master/sds.c">扩容</a>的具体实现：</li>
</ol>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> SDS_MAX_PREALLOC (1024*1024)  <span class="comment">// 这个常量在sds.h定义</span></span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">/* Enlarge the free space at the end of the sds string so that the caller</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">* is sure that after calling this function can overwrite up to addlen</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">* bytes after the end of the string, plus one more byte for nul term.</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">* </span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">* Note: this does not change the *length* of the sds string as returned</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">* by sdslen(), but only the free buffer space we have. */</span></span><br><span class="line">  sds <span class="title function_">sdsMakeRoomFor</span><span class="params">(sds s, <span class="type">size_t</span> addlen)</span> &#123;</span><br><span class="line">   <span class="type">void</span> *sh, *newsh;</span><br><span class="line">   <span class="type">size_t</span> avail = sdsavail(s);</span><br><span class="line">   <span class="type">size_t</span> len, newlen, reqlen;</span><br><span class="line">   <span class="type">char</span> type, oldtype = s[<span class="number">-1</span>] &amp; SDS_TYPE_MASK;</span><br><span class="line">   <span class="type">int</span> hdrlen;</span><br><span class="line"></span><br><span class="line">   <span class="comment">/* Return ASAP if there is enough space left. */</span></span><br><span class="line">   <span class="keyword">if</span> (avail &gt;= addlen) <span class="keyword">return</span> s;</span><br><span class="line"></span><br><span class="line">   len = sdslen(s);</span><br><span class="line">   sh = (<span class="type">char</span>*)s-sdsHdrSize(oldtype);</span><br><span class="line">   reqlen = newlen = (len+addlen);</span><br><span class="line">   <span class="keyword">if</span> (newlen &lt; SDS_MAX_PREALLOC)</span><br><span class="line"></span><br><span class="line">       newlen *= <span class="number">2</span>;</span><br><span class="line"></span><br><span class="line">   <span class="keyword">else</span></span><br><span class="line"></span><br><span class="line">       newlen += SDS_MAX_PREALLOC;</span><br><span class="line"></span><br><span class="line">   type = sdsReqType(newlen);</span><br><span class="line"></span><br><span class="line">   <span class="comment">/* Don&#x27;t use type 5: the user is appending to the string and type 5 is</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">  * not able to remember empty space, so sdsMakeRoomFor() must be called</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">  * at every appending operation. */</span></span><br><span class="line">    <span class="keyword">if</span> (type == SDS_TYPE_5) type = SDS_TYPE_8;</span><br><span class="line"></span><br><span class="line">    hdrlen = sdsHdrSize(type);</span><br><span class="line">    assert(hdrlen + newlen + <span class="number">1</span> &gt; reqlen); <span class="comment">/* Catch size_t overflow */</span></span><br><span class="line">    <span class="keyword">if</span> (oldtype==type) &#123;</span><br><span class="line">     newsh = s_realloc(sh, hdrlen+newlen+<span class="number">1</span>);</span><br><span class="line">     <span class="keyword">if</span> (newsh == <span class="literal">NULL</span>) <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">     s = (<span class="type">char</span>*)newsh+hdrlen;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">     <span class="comment">/* Since the header size changes, need to move the string forward,</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">    * and can&#x27;t use realloc */</span></span><br><span class="line">      newsh = s_malloc(hdrlen+newlen+<span class="number">1</span>);</span><br><span class="line">      <span class="keyword">if</span> (newsh == <span class="literal">NULL</span>) <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">      <span class="built_in">memcpy</span>((<span class="type">char</span>*)newsh+hdrlen, s, len+<span class="number">1</span>);</span><br><span class="line">      s_free(sh);</span><br><span class="line">      s = (<span class="type">char</span>*)newsh+hdrlen;</span><br><span class="line">      s[<span class="number">-1</span>] = type;</span><br><span class="line">      sdssetlen(s, len);</span><br><span class="line">      &#125;</span><br><span class="line">      sdssetalloc(s, newlen);</span><br><span class="line">      <span class="keyword">return</span> s;</span><br><span class="line">      &#125;</span><br></pre></td></tr></table></figure>

<h2 id="压缩列表"><a href="#压缩列表" class="headerlink" title="压缩列表"></a>压缩列表</h2><p>压缩列表(ziplist)是Redis为了节约内存而开发的，它是由连续内存块组成的顺序型数据结构，有点类似于数组,大概的内存布局如下：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">+------------+------------------------+-----------+---------------\</span><br><span class="line">| zlbytes | zltail | zllen | entry1 |  ......  |  entryN |  zlend \</span><br><span class="line">+------------+------------------------+-----------+---------------\</span><br></pre></td></tr></table></figure>

<p>压缩列表在表头有三个字段，在尾部有一个字段，具体解释如下：</p>
<ul>
<li><p>zlbytes：记录整个压缩列表占用对内存字节数；</p>
</li>
<li><p>zltail：记录压缩列表「尾部」节点距离起始地址由多少字节，也就是列表尾的偏移量；</p>
</li>
<li><p>zllen：记录压缩列表包含的节点数量；</p>
</li>
<li><p>zlend：标记压缩列表的结束点，固定值 0xFF（十进制255）。</p>
<p>在压缩列表中，如果我们要查找定位第一个元素和最后一个元素，可以通过表头三个字段（zllen）的长度直接定位，复杂度是 O(1)。<br>而查找其他元素时，就没有这么高效了，只能逐个查找，此时的复杂度就是 O(N) 了，因此压缩列表不适合保存过多的元素。<br>压缩列表节点包含三部分内容：</p>
<ul>
<li>prevlen：记录了「前一个节点」的长度，目的是为了实现从后向前遍历；</li>
<li>encoding：记录了当前节点实际数据的「类型和长度」，类型主要有两种：字符串和整数。</li>
<li>data：记录了当前节点的实际数据，类型和长度都由 encoding 决定；<br>当我们往压缩列表中插入数据时，压缩列表就会根据数据类型是字符串还是整数以及数据的大小，会使用不同空间大小的 prevlen和encoding。<br>这两个元素里保存的信息，这种根据数据大小和类型进行不同的空间大小分配的设计思想，正是Redis为了节省内存而采用的。<br>分别说下，prevlen和encoding是如何根据数据的大小和类型来进行不同的空间大小分配。<br>压缩列表里的每个节点中的 prevlen 属性都记录了「前一个节点的长度」，而且 prevlen 属性的空间大小跟前一个节点长度值有关，比如：</li>
<li>如果前一个节点的长度小于254字节，那么 prevlen 属性需要用1字节的空间来保存这个长度值；</li>
<li>如果前一个节点的长度大于等于254字节，那么 prevlen 属性需要用5字节的空间来保存这个长度值；<br>encoding 属性的空间大小跟数据是字符串还是整数，以及字符串的长度有关：</li>
<li>如果当前节点的数据是整数，则 encoding 会使用 1 字节的空间进行编码，也就是 encoding 长度为 1 字节。通过 encoding 确认了整数类型，就可以确认整数数据的实际大小了，比如如果 encoding 编码确认了数据是 int16 整数，那么 data 的长度就是 int16 的大小。</li>
<li>如果当前节点的数据是字符串，根据字符串的长度大小，encoding 会使用1字节/2字节/5字节的空间进行编码，encoding 编码的前两个 bit 表示数据的类型，后续的其他 bit 标识字符串数据的实际长度，即 data 的长度。</li>
</ul>
</li>
</ul>
<p>压缩列表除了查找复杂度高的问题，还有一个问题：<br>压缩列表新增某个元素或修改某个元素时，如果空间不不够，压缩列表占用的内存空间就需要重新分配。<br>而当新插入的元素较大时，可能会导致后续元素的 prevlen占用空间都发生变化，从而引起「连锁更新」问题， 导致每个元素的空间都要重新分配，造成访问压缩列表性能的下降。<br>连锁虽然可以用紧凑的内存布局节省开销，但是元素数量越来越多，内存重新分配，连锁问题显而易见，因此压缩列表只适用于节点数量较小的场景。</p>
<h2 id="quicklist"><a href="#quicklist" class="headerlink" title="quicklist"></a>quicklist</h2><p>quicklist的结构体跟双向链表的结构体类似，都包含了表头和表尾，区别在于quicklist的节点是quicklistNode。也多了quicklistNodes个数。<br>quicklist其实就是封装了双向链表，每个链表节点不再保存元素值，而是保存了一个压缩列表。<br>在向 quicklist 添加一个元素的时候，不会像普通的链表那样，直接新建一个链表节点。而是会检查插入位置的压缩列表是否能容纳该元素，如果能容纳就直接保存到quicklistNode结构里的压缩列表，如果不能容纳，才会新建一个新的 quicklistNode 结构。<br>quicklist 会控制 quicklistNode 结构里的压缩列表的大小或者元素个数，来规避潜在的连锁更新的风险，但是这并没有完全解决连锁更新的问题。</p>
<h2 id="listpack"><a href="#listpack" class="headerlink" title="listpack"></a>listpack</h2><p>quicklist 虽然通过控制quicklistNode结构里的压缩列表的大小或者元素个数，来减少连锁更新带来的性能影响，但是并没有完全解决连锁更新的问题。<br>因为 quicklistNode 还是用了压缩列表来保存元素，压缩列表连锁更新的问题，来源于它的结构设计，所以要想彻底解决这个问题，需要设计一个新的数据结构。<br>于是，Redis 在 5.0 新设计一个数据结构叫 listpack，目的是替代压缩列表，它最大特点是 listpack中每个节点不再包含前一个节点的长度了，压缩列表每个节点正因为需要保存前一个节点的长度字段，就会有连锁更新的隐患。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">+------------+------------------------+-----------+-------------------------\</span><br><span class="line">| listpack总字节数 |listpack元素数量 | entry1 |  ...  |  entryN |  listpack结尾 \</span><br><span class="line">+------------+------------------------+-----------+--------------------------\</span><br></pre></td></tr></table></figure>

<p>listpack中entry节点包含三部分内容：</p>
<ul>
<li>encoding，定义该元素的编码类型，会对不同长度的整数和字符串进行编码；</li>
<li>data，实际存放的数据；</li>
<li>len，encoding+data的总长度；</li>
</ul>
<p>可以看到，listpack没有压缩列表中记录前一个节点长度的字段了，listpack只记录当前节点的长度，当我们向 listpack 加入一个新元素的时候，不会影响其他节点的长度字段的变化，从而避免了压缩列表的连锁更新问题。</p>
<h2 id="哈希表"><a href="#哈希表" class="headerlink" title="哈希表"></a>哈希表</h2><p>hash table,不多说什么，日常最常见也最常用的数据结构，它能以O(1)的时间复杂度快速查找数据。将key通过hash函数计算就能定位到元素的位置。<br>随着数据量的增大，那么哈希冲突的可能性会越来越高，解决哈希冲突的方案有：二次线性探测、链式(HashMap)处理。<br>Redis的hash采用链式进行哈希冲突处理，当内存不够是也会再次rehash。<br>值得注意的是rehash有数据迁移的过程，Redis采用渐进式rehash操作，避免一次性rehash数据拷贝引发的性能问题。<br>rehash触发的条件与负载因子有关系：负载因子=哈希表已保存节点数量/哈希表大小。触发rehash的条件有两个：</p>
<ol>
<li>当负载因子&gt;=1 ，并且Redis没有在执行bgsave命令或者bgrewiteaof命令，也就是没有执行RDB快照或没有进行AOF重写的时候，就会进行rehash操作。</li>
<li>当负载因子大于等于 5 时，此时说明哈希冲突非常严重了，不管有没有有在执行RDB快照或AOF重写，都会强制进行rehash操作。</li>
</ol>
<h2 id="整数集合"><a href="#整数集合" class="headerlink" title="整数集合"></a>整数集合</h2><p>inset整数集合本质上是一块固定类型的连续内存空间。</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">intset</span> &#123;</span></span><br><span class="line">    <span class="comment">//编码方式</span></span><br><span class="line">    <span class="type">uint32_t</span> encoding;</span><br><span class="line">    <span class="comment">//集合包含的元素数量</span></span><br><span class="line">    <span class="type">uint32_t</span> length;</span><br><span class="line">    <span class="comment">//保存元素的数组</span></span><br><span class="line">    <span class="type">int8_t</span> contents[];</span><br><span class="line">&#125; ;</span><br></pre></td></tr></table></figure>

<ul>
<li>如果 encoding 属性值为 INTSET_ENC_INT16，那么 contents 就是一个 int16_t 类型的数组，数组中每一个元素的类型都是 int16_t；</li>
<li>如果 encoding 属性值为 INTSET_ENC_INT32，那么 contents 就是一个 int32_t 类型的数组，数组中每一个元素的类型都是 int32_t；</li>
<li>如果 encoding 属性值为 INTSET_ENC_INT64，那么 contents 就是一个 int64_t 类型的数组，数组中每一个元素的类型都是 int64_t；</li>
</ul>
<p>整数集合会有一个升级规则，就是当我们将一个新元素加入到整数集合里面，如果新元素的类型（int32_t）比整数集合现有所有元素的类型（int16_t）都要长时，整数集合需要先进行升级，也就是按新元素的类型（int32_t）扩展 contents 数组的空间大小，然后才能将新元素加入到整数集合里，当然升级的过程中，也要维持整数集合的有序性。</p>
<p>一旦对数组进行了升级，就会一直保持升级后的状态，就无法降级。</p>
<h2 id="跳表"><a href="#跳表" class="headerlink" title="跳表"></a>跳表</h2><p>链表在查找元素的时候，因为需要逐一查找，所以查询效率非常低，时间复杂度是O(N)，于是就出现了跳表。</p>
<p>zskiplist，<strong>跳表是在链表基础上改进过来的，实现了一种「多层」的有序链表</strong>，这样的好处是能快读定位数据。</p>
<p>Redis 只有 Zset 对象的底层实现用到了跳表，跳表的优势是能支持平均 O(logN) 复杂度的节点查找。</p>
<h1 id="协议"><a href="#协议" class="headerlink" title="协议"></a>协议</h1><h2 id="RESP序列化协议"><a href="#RESP序列化协议" class="headerlink" title="RESP序列化协议"></a>RESP序列化协议</h2><p>Redis序列化协议(Redis serialization protoco specification)， 简称RESP。是Redis客户端与Redis服务器端通信的语言。</p>
<p>RESP的特点：</p>
<ol>
<li><p>实现比较简单</p>
</li>
<li><p>解析快速</p>
</li>
<li><p>可读性好</p>
</li>
<li><p>二进制传输，安全性好</p>
</li>
</ol>
<p>RESP支持序列化不同的数据类型：整数、简单字符串、大容量字符串、数组和特定的错误类型。</p>
<p>通信请求作为字符串数组从客户端发送到Redis Server，字符串数组里就是要执行命令参数。</p>
<p>同样，Redis Server使用特定于命令的数据类型进行客户端响应。</p>
<p>注意：RESP只是Redis客户端与服务器端的交互，而非Redis集群节点之间的通信协议。</p>
<p>RESP 协议是在 Redis 1.2中引入的，在Redis2.0后成为客户端与服务器端的标准方式。目前C++中的hiredis、Java中的Jedis库都是基于RESP实现的。</p>
<p>在RESP中，第一个字节决定了数据结构：</p>
<ul>
<li><p>简单字符串：第一个字节为 “<code>+</code>“</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;+OK\r\n&quot;</span>  <span class="comment"># 简单字符串</span></span><br></pre></td></tr></table></figure></li>
<li><p>错误： 第一个字节为”<code>-</code>“</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;-Error message\r\n&quot;</span>  <span class="comment"># 错误类型</span></span><br></pre></td></tr></table></figure></li>
<li><p>整数： 第一个字节为”<code>:</code>“</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;:0\r\n&quot;</span>  <span class="comment"># 整数类型表示</span></span><br></pre></td></tr></table></figure></li>
<li><p>大容量字符串:第一个字节为”<code>$</code>“</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;<span class="variable">$5</span>\r\nhello\r\n&quot;</span>  <span class="comment"># 大容量类型表示，不超过512MB</span></span><br></pre></td></tr></table></figure></li>
<li><p>数组：第一个字节为”<code>*</code>“</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;*2\r\n<span class="variable">$5</span>\r\nhello\r\n<span class="variable">$5</span>\r\nworld\r\n&quot;</span> <span class="comment"># 数组类型表示，实现helloworld通信</span></span><br></pre></td></tr></table></figure></li>
</ul>
<p>RESP可以使用后面指定的大容量字符串或数组的特殊变体来表示 Null 值。</p>
<p>在 RESP中，协议的不同部分总是以”<code>\r\n</code>“(CRLF)结束。</p>
<p>关于RESP协议，Netty已经为我们定义了相关的类，我们可以参照io.netty.handler.codec.redis.RedisMessageType类可以自己实现一个类似与Jedis的Redis客户端。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> io.netty.handler.codec.redis;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> io.netty.buffer.ByteBuf;</span><br><span class="line"><span class="keyword">import</span> io.netty.util.internal.UnstableApi;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Type of &lt;a href=&quot;http://redis.io/topics/protocol&quot;&gt;RESP (REdis Serialization Protocol)&lt;/a&gt;.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@UnstableApi</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">enum</span> <span class="title class_">RedisMessageType</span> &#123;</span><br><span class="line"></span><br><span class="line">    INLINE_COMMAND(<span class="literal">null</span>, <span class="literal">true</span>),</span><br><span class="line">    SIMPLE_STRING((<span class="type">byte</span>) <span class="string">&#x27;+&#x27;</span>, <span class="literal">true</span>),</span><br><span class="line">    ERROR((<span class="type">byte</span>) <span class="string">&#x27;-&#x27;</span>, <span class="literal">true</span>),</span><br><span class="line">    INTEGER((<span class="type">byte</span>) <span class="string">&#x27;:&#x27;</span>, <span class="literal">true</span>),</span><br><span class="line">    BULK_STRING((<span class="type">byte</span>) <span class="string">&#x27;$&#x27;</span>, <span class="literal">false</span>),</span><br><span class="line">    ARRAY_HEADER((<span class="type">byte</span>) <span class="string">&#x27;*&#x27;</span>, <span class="literal">false</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Byte value;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">boolean</span> inline;</span><br><span class="line"></span><br><span class="line">    RedisMessageType(Byte value, <span class="type">boolean</span> inline) &#123;</span><br><span class="line">        <span class="built_in">this</span>.value = value;</span><br><span class="line">        <span class="built_in">this</span>.inline = inline;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Returns length of this type.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">length</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> value != <span class="literal">null</span> ? RedisConstants.TYPE_LENGTH : <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Returns &#123;<span class="doctag">@code</span> true&#125; if this type is inline type, or returns &#123;<span class="doctag">@code</span> false&#125;. If this is &#123;<span class="doctag">@code</span> true&#125;,</span></span><br><span class="line"><span class="comment">     * this type doesn&#x27;t have length field.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isInline</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> inline;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Determine &#123;<span class="doctag">@link</span> RedisMessageType&#125; based on the type prefix &#123;<span class="doctag">@code</span> byte&#125; read from given the buffer.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> RedisMessageType <span class="title function_">readFrom</span><span class="params">(ByteBuf in, <span class="type">boolean</span> decodeInlineCommands)</span> &#123;</span><br><span class="line">        <span class="keyword">final</span> <span class="type">int</span> <span class="variable">initialIndex</span> <span class="operator">=</span> in.readerIndex();</span><br><span class="line">        <span class="keyword">final</span> <span class="type">RedisMessageType</span> <span class="variable">type</span> <span class="operator">=</span> valueOf(in.readByte());</span><br><span class="line">        <span class="keyword">if</span> (type == INLINE_COMMAND) &#123;</span><br><span class="line">            <span class="keyword">if</span> (!decodeInlineCommands) &#123;</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RedisCodecException</span>(<span class="string">&quot;Decoding of inline commands is disabled&quot;</span>);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// reset index to make content readable again</span></span><br><span class="line">            in.readerIndex(initialIndex);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> type;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Write the message type&#x27;s prefix to the given buffer.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">writeTo</span><span class="params">(ByteBuf out)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (value == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        out.writeByte(value.byteValue());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> RedisMessageType <span class="title function_">valueOf</span><span class="params">(<span class="type">byte</span> value)</span> &#123;</span><br><span class="line">        <span class="keyword">switch</span> (value) &#123;</span><br><span class="line">        <span class="keyword">case</span> <span class="string">&#x27;+&#x27;</span>:</span><br><span class="line">            <span class="keyword">return</span> SIMPLE_STRING;</span><br><span class="line">        <span class="keyword">case</span> <span class="string">&#x27;-&#x27;</span>:</span><br><span class="line">            <span class="keyword">return</span> ERROR;</span><br><span class="line">        <span class="keyword">case</span> <span class="string">&#x27;:&#x27;</span>:</span><br><span class="line">            <span class="keyword">return</span> INTEGER;</span><br><span class="line">        <span class="keyword">case</span> <span class="string">&#x27;$&#x27;</span>:</span><br><span class="line">            <span class="keyword">return</span> BULK_STRING;</span><br><span class="line">        <span class="keyword">case</span> <span class="string">&#x27;*&#x27;</span>:</span><br><span class="line">            <span class="keyword">return</span> ARRAY_HEADER;</span><br><span class="line">        <span class="keyword">default</span>:</span><br><span class="line">            <span class="keyword">return</span> INLINE_COMMAND;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>当然我们用telnet命令也可以访问Redis，首先我们书写协议：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># set name lisi协议编写</span></span><br><span class="line">*3</span><br><span class="line"><span class="variable">$3</span></span><br><span class="line"><span class="built_in">set</span></span><br><span class="line"><span class="variable">$4</span></span><br><span class="line">name</span><br><span class="line"><span class="variable">$4</span></span><br><span class="line">lisi</span><br><span class="line"><span class="comment"># get name 协议编写</span></span><br><span class="line">*2</span><br><span class="line"><span class="variable">$3</span></span><br><span class="line">get</span><br><span class="line"><span class="variable">$4</span></span><br><span class="line">name</span><br></pre></td></tr></table></figure>

<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">[root@lt <span class="built_in">local</span>]<span class="comment"># telnet 192.168.1.21 10359</span></span><br><span class="line">Trying 192.168.1.21...</span><br><span class="line">Connected to 192.168.1.21.</span><br><span class="line">Escape character is <span class="string">&#x27;^]&#x27;</span>.</span><br><span class="line">*1</span><br><span class="line"><span class="variable">$4</span></span><br><span class="line">PING</span><br><span class="line">+PONG </span><br><span class="line"></span><br><span class="line">*3</span><br><span class="line"><span class="variable">$3</span></span><br><span class="line"><span class="built_in">set</span></span><br><span class="line"><span class="variable">$4</span></span><br><span class="line">name</span><br><span class="line"><span class="variable">$4</span></span><br><span class="line">lisi</span><br><span class="line"></span><br><span class="line">*2</span><br><span class="line"><span class="variable">$3</span></span><br><span class="line">get</span><br><span class="line"><span class="variable">$4</span></span><br><span class="line">name</span><br></pre></td></tr></table></figure>

<p>接下来，我们可以用redis-cli等其他客户端工具进行验证：是否存在key为name的数据。</p>
<h2 id="Gossip协议"><a href="#Gossip协议" class="headerlink" title="Gossip协议"></a>Gossip协议</h2><p>Redis集群中各个节点之间通信的通信协议。</p>
<p>定义：节点每秒随机选择超时的节点进行通信，包含其他节点的信息，最终全部节点都会获得集群的完整信息。</p>
<p>节点并不要求即时生效，通过随机选择节点，传递本地已知信息的方式，达到集群信息在各节点中的最终一致的效果。</p>
<p>Redis节点间通信的消息类型：</p>
<ul>
<li><strong>Ping消息</strong>：用于检测节点是否在线，或者交换信息，也就是我们刚才提到的心跳检测时会使用的</li>
<li><strong>Pong消息</strong>：接受到Ping消息的节点，会响应一个Pong格式的消息，将自身的状态返回给请求的节点</li>
<li><strong>Meet消息</strong>：通知新节点的加入。</li>
<li><strong>Fail消息</strong>：广播节点下线，比如出现了节点故障，摘除机器后需要让节点知道不再和已下线的节点进行通信了</li>
<li><strong>Publish消息</strong>：其他数据监听机制下的广播消息。</li>
<li><strong>Failover消息</strong>：出现故障时，进行选举的请求和投票。</li>
<li><strong>Update消息</strong>：Redis的一致性哈希数据分区，会有槽和节点的对应关系，如果发生扩容或缩容，需要告诉节点进行槽的更新。</li>
</ul>
<h1 id="性能调优"><a href="#性能调优" class="headerlink" title="性能调优"></a>性能调优</h1><p>TODO，运维范畴，日后补充。</p>
<h1 id="帮助文档"><a href="#帮助文档" class="headerlink" title="帮助文档"></a>帮助文档</h1><ul>
<li><p><a target="_blank" rel="noopener" href="https://redis.io/docs/data-types/">Redis数据类型</a></p>
</li>
<li><p><a target="_blank" rel="noopener" href="https://redis.io/commands/">Redis命令指南</a></p>
</li>
<li><p><a target="_blank" rel="noopener" href="https://github.com/redis/redis">Redis源代码</a></p>
</li>
<li><p><a target="_blank" rel="noopener" href="http://api.map.baidu.com/lbsapi/getpoint/">百度地图拾取坐标系统</a></p>
</li>
<li><p><a target="_blank" rel="noopener" href="https://lbs.amap.com/tools/picker">高德地图坐标拾取器</a></p>
</li>
<li><p><a target="_blank" rel="noopener" href="https://redis.io/docs/reference/protocol-spec/">RESP规范</a></p>
</li>
<li><p><a target="_blank" rel="noopener" href="https://xiaolincoding.com/redis/">图解Redis介绍 | 小林coding</a></p>
</li>
</ul>
</section>
    <!-- Tags START -->
    
      <div class="tags">
        <span>Tags:</span>
        
  <a href="/tags#Redis" >
    <span class="tag-code">Redis</span>
  </a>

      </div>
    
    <!-- Tags END -->
    <!-- NAV START -->
    
  <div class="nav-container">
    <!-- reverse left and right to put prev and next in a more logic postition -->
    
      <a class="nav-left" href="/2022/05/07/Zookeeper%E4%B8%93%E9%A2%98-Zookeeper%E5%85%A5%E9%97%A8/">
        <span class="nav-arrow">← </span>
        
          Zookeeper专题-Zookeeper入门
        
      </a>
    
    
      <a class="nav-right" href="/2023/02/01/%E3%80%90%E7%94%9F%E6%B4%BB%E3%80%91%E4%BA%8C%E6%89%8B%E6%88%BF%E8%B4%AD%E4%B9%B0%E6%8C%87%E5%8D%97/">
        
          【生活】二手房购买指南
        
        <span class="nav-arrow"> →</span>
      </a>
    
  </div>

    <!-- NAV END -->
    <!-- 打赏 START -->
    
      <div class="money-like">
        <div class="reward-btn">
          赏
          <span class="money-code">
            <span class="alipay-code">
              <div class="code-image"></div>
              <b>使用支付宝打赏</b>
            </span>
            <span class="wechat-code">
              <div class="code-image"></div>
              <b>使用微信打赏</b>
            </span>
          </span>
        </div>
        <p class="notice">若你觉得我的文章对你有帮助，欢迎点击上方按钮对我打赏</p>
      </div>
    
    <!-- 打赏 END -->
    <!-- 二维码 START -->
    
      <div class="qrcode">
        <canvas id="share-qrcode"></canvas>
        <p class="notice">扫描二维码，分享此文章</p>
      </div>
    
    <!-- 二维码 END -->
    
      <!-- Utterances START -->
      <div id="utterances"></div>
      <script src="https://utteranc.es/client.js"
        repo="ltyeamin/blogtalks"
        issue-term="pathname"
        theme="github-light"
        crossorigin="anonymous"
        async></script>    
      <!-- Utterances END -->
    
  </article>
  <!-- Article END -->
  <!-- Catalog START -->
  
    <aside class="catalog-container">
  <div class="toc-main">
    <strong class="toc-title">Catalog</strong>
    
      <ol class="toc-nav"><li class="toc-nav-item toc-nav-level-1"><a class="toc-nav-link" href="#%E5%B8%B8%E7%94%A8%E7%B1%BB%E5%9E%8B"><span class="toc-nav-text">常用类型</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#String"><span class="toc-nav-text">String</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E5%9F%BA%E6%9C%AC%E6%93%8D%E4%BD%9C"><span class="toc-nav-text">基本操作</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E6%89%A9%E5%B1%95%E8%AF%B4%E6%98%8E"><span class="toc-nav-text">扩展说明</span></a></li></ol></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#Hash"><span class="toc-nav-text">Hash</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E5%9F%BA%E6%9C%AC%E6%93%8D%E4%BD%9C-1"><span class="toc-nav-text">基本操作</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E6%89%A9%E5%B1%95%E8%AF%B4%E6%98%8E-1"><span class="toc-nav-text">扩展说明</span></a></li></ol></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#List"><span class="toc-nav-text">List</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E6%89%A9%E5%B1%95%E8%AF%B4%E6%98%8E-2"><span class="toc-nav-text">扩展说明</span></a></li></ol></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#Set"><span class="toc-nav-text">Set</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E5%9F%BA%E6%9C%AC%E6%93%8D%E4%BD%9C-2"><span class="toc-nav-text">基本操作</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E6%89%A9%E5%B1%95%E8%AF%B4%E6%98%8E-3"><span class="toc-nav-text">扩展说明</span></a></li></ol></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#ZSet"><span class="toc-nav-text">ZSet</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E5%9F%BA%E6%9C%AC%E6%93%8D%E4%BD%9C-3"><span class="toc-nav-text">基本操作</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E6%89%A9%E5%B1%95%E8%AF%B4%E6%98%8E-4"><span class="toc-nav-text">扩展说明</span></a></li></ol></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#BitMap"><span class="toc-nav-text">BitMap</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E5%9F%BA%E6%9C%AC%E6%93%8D%E4%BD%9C-4"><span class="toc-nav-text">基本操作</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E6%89%A9%E5%B1%95%E8%AF%B4%E6%98%8E-5"><span class="toc-nav-text">扩展说明</span></a></li></ol></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#GEO"><span class="toc-nav-text">GEO</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E5%9F%BA%E6%9C%AC%E6%93%8D%E4%BD%9C-5"><span class="toc-nav-text">基本操作</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E6%89%A9%E5%B1%95%E8%AF%B4%E6%98%8E-6"><span class="toc-nav-text">扩展说明</span></a></li></ol></li></ol></li><li class="toc-nav-item toc-nav-level-1"><a class="toc-nav-link" href="#%E8%BF%87%E6%9C%9F%E4%B8%8E%E6%B7%98%E6%B1%B0"><span class="toc-nav-text">过期与淘汰</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#%E8%BF%87%E6%9C%9F%E5%88%A0%E9%99%A4%E6%9C%BA%E5%88%B6"><span class="toc-nav-text">过期删除机制</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E5%AE%9A%E6%97%B6%E5%88%A0%E9%99%A4"><span class="toc-nav-text">定时删除</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E6%83%B0%E6%80%A7%E5%88%A0%E9%99%A4"><span class="toc-nav-text">惰性删除</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E5%AE%9A%E6%9C%9F%E5%88%A0%E9%99%A4"><span class="toc-nav-text">定期删除</span></a></li></ol></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#%E5%86%85%E5%AD%98%E6%B7%98%E6%B1%B0%E7%AD%96%E7%95%A5"><span class="toc-nav-text">内存淘汰策略</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E5%85%AB%E7%A7%8D%E7%AD%96%E7%95%A5"><span class="toc-nav-text">八种策略</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E8%BF%91LRU%E7%AE%97%E6%B3%95"><span class="toc-nav-text">近LRU算法</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#LFU%E7%AE%97%E6%B3%95"><span class="toc-nav-text">LFU算法</span></a></li></ol></li></ol></li><li class="toc-nav-item toc-nav-level-1"><a class="toc-nav-link" href="#%E5%BA%94%E7%94%A8%E7%BC%93%E5%AD%98"><span class="toc-nav-text">应用缓存</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#%E7%BC%93%E5%AD%98%E5%BC%82%E5%B8%B8"><span class="toc-nav-text">缓存异常</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E7%BC%93%E5%AD%98%E9%A2%84%E7%83%AD"><span class="toc-nav-text">缓存预热</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E7%BC%93%E5%AD%98%E9%9B%AA%E5%B4%A9"><span class="toc-nav-text">缓存雪崩</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E7%BC%93%E5%AD%98%E5%87%BB%E7%A9%BF"><span class="toc-nav-text">缓存击穿</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E7%BC%93%E5%AD%98%E7%A9%BF%E9%80%8F"><span class="toc-nav-text">缓存穿透</span></a></li></ol></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#%E7%BC%93%E5%AD%98%E4%B8%80%E8%87%B4%E6%80%A7"><span class="toc-nav-text">缓存一致性</span></a></li></ol></li><li class="toc-nav-item toc-nav-level-1"><a class="toc-nav-link" href="#%E9%AB%98%E5%8F%AF%E7%94%A8"><span class="toc-nav-text">高可用</span></a></li><li class="toc-nav-item toc-nav-level-1"><a class="toc-nav-link" href="#%E6%8C%81%E4%B9%85%E5%8C%96"><span class="toc-nav-text">持久化</span></a></li><li class="toc-nav-item toc-nav-level-1"><a class="toc-nav-link" href="#%E5%86%85%E9%83%A8%E5%AE%9E%E7%8E%B0"><span class="toc-nav-text">内部实现</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#SDS%E7%AE%80%E5%8D%95%E5%8A%A8%E6%80%81%E5%AD%97%E7%AC%A6%E4%B8%B2"><span class="toc-nav-text">SDS简单动态字符串</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#%E5%8E%8B%E7%BC%A9%E5%88%97%E8%A1%A8"><span class="toc-nav-text">压缩列表</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#quicklist"><span class="toc-nav-text">quicklist</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#listpack"><span class="toc-nav-text">listpack</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#%E5%93%88%E5%B8%8C%E8%A1%A8"><span class="toc-nav-text">哈希表</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#%E6%95%B4%E6%95%B0%E9%9B%86%E5%90%88"><span class="toc-nav-text">整数集合</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#%E8%B7%B3%E8%A1%A8"><span class="toc-nav-text">跳表</span></a></li></ol></li><li class="toc-nav-item toc-nav-level-1"><a class="toc-nav-link" href="#%E5%8D%8F%E8%AE%AE"><span class="toc-nav-text">协议</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#RESP%E5%BA%8F%E5%88%97%E5%8C%96%E5%8D%8F%E8%AE%AE"><span class="toc-nav-text">RESP序列化协议</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#Gossip%E5%8D%8F%E8%AE%AE"><span class="toc-nav-text">Gossip协议</span></a></li></ol></li><li class="toc-nav-item toc-nav-level-1"><a class="toc-nav-link" href="#%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98"><span class="toc-nav-text">性能调优</span></a></li><li class="toc-nav-item toc-nav-level-1"><a class="toc-nav-link" href="#%E5%B8%AE%E5%8A%A9%E6%96%87%E6%A1%A3"><span class="toc-nav-text">帮助文档</span></a></li></ol>
    
  </div>
</aside>
  
  <!-- Catalog END -->
</main>

<script>
  (function () {
    var url = 'http://example.com/2023/01/07/全方位玩转Redis/';
    var banner = ''
    if (banner !== '' && banner !== 'undefined' && banner !== 'null') {
      $('#article-banner').css({
        'background-image': 'url(' + banner + ')'
      })
    } else {
      $('#article-banner').geopattern(url)
    }
    $('.header').removeClass('fixed-header')

    // error image
    $(".markdown-content img").on('error', function() {
      $(this).attr('src', '/css/images/error_icon.png')
      $(this).css({
        'cursor': 'default'
      })
    })

    // zoom image
    $(".markdown-content img").on('click', function() {
      var src = $(this).attr('src')
      if (src !== '/css/images/error_icon.png') {
        var imageW = $(this).width()
        var imageH = $(this).height()

        var zoom = ($(window).width() * 0.95 / imageW).toFixed(2)
        zoom = zoom < 1 ? 1 : zoom
        zoom = zoom > 2 ? 2 : zoom
        var transY = (($(window).height() - imageH) / 2).toFixed(2)

        $('body').append('<div class="image-view-wrap"><div class="image-view-inner"><img src="'+ src +'" /></div></div>')
        $('.image-view-wrap').addClass('wrap-active')
        $('.image-view-wrap img').css({
          'width': `${imageW}`,
          'transform': `translate3d(0, ${transY}px, 0) scale3d(${zoom}, ${zoom}, 1)`
        })
        $('html').css('overflow', 'hidden')

        $('.image-view-wrap').on('click', function() {
          $(this).remove()
          $('html').attr('style', '')
        })
      }
    })
  })();
</script>


  <script>
    var qr = new QRious({
      element: document.getElementById('share-qrcode'),
      value: document.location.href
    });
  </script>






    <div class="scroll-top">
  <span class="arrow-icon"></span>
</div>
    <footer class="app-footer">
  <p class="copyright">
    &copy; 2024 | Proudly powered by <a href="https://hexo.io" target="_blank">Hexo</a>
    <br>
    Theme by <a target="_blank" rel="noopener" href="https://github.com/ltyeamin">tong.li</a>
  </p>
</footer>

<script>
  function async(u, c) {
    var d = document, t = 'script',
      o = d.createElement(t),
      s = d.getElementsByTagName(t)[0];
    o.src = u;
    if (c) { o.addEventListener('load', function (e) { c(null, e); }, false); }
    s.parentNode.insertBefore(o, s);
  }
</script>
<script>
  async("https://cdn.staticfile.org/fastclick/1.0.6/fastclick.min.js", function(){
    FastClick.attach(document.body);
  })
</script>

<script>
  var hasLine = 'true';
  async("https://cdn.staticfile.org/highlight.js/9.12.0/highlight.min.js", function(){
    $('figure pre').each(function(i, block) {
      var figure = $(this).parents('figure');
      if (hasLine === 'false') {
        figure.find('.gutter').hide();
      }
      hljs.configure({useBR: true});
      var lang = figure.attr('class').split(' ')[1] || 'code';
      var codeHtml = $(this).html();
      var codeTag = document.createElement('code');
      codeTag.className = lang;
      codeTag.innerHTML = codeHtml;
      $(this).attr('class', '').empty().html(codeTag);
      figure.attr('data-lang', lang.toUpperCase());
      hljs.highlightBlock(block);
    });
  })
</script>
<!-- Baidu Tongji -->



<script src='https://cdn.staticfile.org/mermaid/8.11.2/mermaid.min.js'></script>



<script src="/js/script.js"></script>


  </body>
</html>